﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;

namespace Loki.Model
{
	public class IndexedKeyMapper<TEntityClass, TEntityIndex> : LokiObject, IDisposable, IKeyMapper<TEntityClass>
	{

		#region Disposable
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="IndexedKeyMapper" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="IndexedKeyMapper" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~IndexedKeyMapper()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="IndexedKeyMapper" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				_Lock.Dispose();
				_IndexStore.Clear();
				_FullLoaded.Clear();
			}
		}
		#endregion

		public IndexedKeyMapper(IIndexConfiguration<TEntityClass> P_Configuration, IEqualityComparer<TEntityClass> P_ClassComparer = null)
		{
			// check config
			IndexConfiguration<TEntityClass, TEntityIndex> L_Config = P_Configuration as IndexConfiguration<TEntityClass, TEntityIndex>;
			if (L_Config == null)
				throw BuildErrorFormat(ToolsError.TOO_003, P_Configuration.GetType(), GetType());

			// init store
			if (L_Config.IndexComparer != null)
			{
				_IndexStore = new Dictionary<TEntityIndex, HashSet<TEntityClass>>(L_Config.IndexComparer);
				_FullLoaded = new HashSet<TEntityIndex>(L_Config.IndexComparer);
				_Invalidated = new HashSet<TEntityIndex>(L_Config.IndexComparer);
			}
			else
			{
				_IndexStore = new Dictionary<TEntityIndex, HashSet<TEntityClass>>();
				_FullLoaded = new HashSet<TEntityIndex>();
				_Invalidated = new HashSet<TEntityIndex>();
			}


			_Lock = new ReaderWriterLockSlim();
			Func<TEntityClass, object> _BulkIndexer = L_Config.Indexer.Compile();
			_Indexer = x => (TEntityIndex)_BulkIndexer(x);

			_ClassComparer = P_ClassComparer;

		}

		private ReaderWriterLockSlim _Lock;
		private Dictionary<TEntityIndex, HashSet<TEntityClass>> _IndexStore;
		private HashSet<TEntityIndex> _FullLoaded;
		private HashSet<TEntityIndex> _Invalidated;
		private Func<TEntityClass, TEntityIndex> _Indexer;
		private IEqualityComparer<TEntityClass> _ClassComparer;

		public IEnumerable<TEntityClass> GetAllByKey(object P_Key)
		{
			return _Lock.WithRead(() => _GetAllByKey((TEntityIndex)P_Key));
		}

		private IEnumerable<TEntityClass> _GetAllByKey(TEntityIndex P_Key)
		{
			if (_IndexStore.ContainsKey(P_Key))
				return _IndexStore[P_Key];
			else
				return new List<TEntityClass>();
		}

		public void Reset()
		{
			_Lock.WithWrite(_Reset);
		}

		private void _Reset()
		{
			_FullLoaded.Clear();
			_IndexStore.Clear();
		}

		public bool Register(TEntityClass P_Entity)
		{
			return _Lock.WithWrite(() => _Register(P_Entity));
		}

		private bool _Register(TEntityClass P_Entity)
		{
			TEntityIndex L_EntityHash = _Indexer(P_Entity);

			if (L_EntityHash != null)
			{
				if (!_IndexStore.ContainsKey(L_EntityHash))
				{
					if (_ClassComparer != null)
						_IndexStore.Add(L_EntityHash, new HashSet<TEntityClass>(_ClassComparer));
					else
						_IndexStore.Add(L_EntityHash, new HashSet<TEntityClass>());
				}
				return _IndexStore[L_EntityHash].Add(P_Entity);
			}
			return false;
		}

		public void Remove(TEntityClass P_Entity)
		{
			_Lock.WithWrite(() => _Remove(P_Entity));
		}

		private void _Remove(TEntityClass P_Entity)
		{
			TEntityIndex L_EntityHash = _Indexer(P_Entity);

			if (L_EntityHash != null)
			{
				// registered hash
				if (_IndexStore.ContainsKey(L_EntityHash) && _IndexStore[L_EntityHash].Contains(P_Entity))
				{
					// remove registered entity
					_IndexStore[L_EntityHash].Remove(P_Entity);
				}
			}
		}

		public bool IsLoaded(object P_Key)
		{
			return _Lock.WithWrite(() => _IsLoaded((TEntityIndex)P_Key));
		}

		private bool _IsLoaded(TEntityIndex P_Index)
		{
			return _FullLoaded.Contains(P_Index);
		}

		public void MarkAsLoaded(object P_Key)
		{
			_Lock.WithWrite(() => _MarkAsLoaded((TEntityIndex)P_Key));
		}

		private void _MarkAsLoaded(TEntityIndex P_Key)
		{
			_FullLoaded.Add(P_Key);
			_Invalidated.Remove(P_Key);
		}

		public void Invalidate(object P_Key)
		{
			_Lock.WithWrite(() => _Invalidate((TEntityIndex)P_Key));
		}

		private void _Invalidate(TEntityIndex P_Key)
		{
			if (_FullLoaded.Contains(P_Key))
				_FullLoaded.Remove(P_Key);
			if (_IndexStore.ContainsKey(P_Key))
				_IndexStore.Remove(P_Key);
			_Invalidated.Add(P_Key);
		}

		public bool IsInvalidated(object P_Key)
		{
			return _Lock.WithRead(() => _IsInvalidated((TEntityIndex)P_Key));
		}

		private bool _IsInvalidated(TEntityIndex P_Key)
		{
			return _Invalidated.Contains(P_Key);
		}

		public object ComputeIndex(TEntityClass P_Entity)
		{
			return _Indexer(P_Entity);
		}
	}
}
